Skip to content

fix(widget): show context-limit message on HTTP 429 token budget exceeded and disable composer - #62

Merged
AmitAvital1 merged 7 commits into
extra-org:mainfrom
rishu685:fix/issue-50-budget-exceeded-message
Aug 1, 2026
Merged

fix(widget): show context-limit message on HTTP 429 token budget exceeded and disable composer#62
AmitAvital1 merged 7 commits into
extra-org:mainfrom
rishu685:fix/issue-50-budget-exceeded-message

Conversation

@rishu685

@rishu685 rishu685 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #50.

This PR implements a backend-driven solution to display a clear, user-friendly context-limit error message when a conversation hits its token budget (HTTP 429 ConversationTokenBudgetExceeded), rather than the generic "Something went wrong. Please try again." message.

To address reviewer concerns from PR #51 and #53 (statelessness of the frontend), we propagate the backend's explicit JSON detail error payload to the widget, allowing the backend to dictate the error message format directly.

Changes

  1. Backend: Changed HTTP 429 detail response to "This conversation has reached its context limit. Start a new chat to continue.".
    2.Backend Test: Added Python unit tests checking 429 response codes and custom detail payloads in stream and non-stream endpoints.
  2. Frontend API: Refactored request helpers to extract the backend's custom JSON detail field and attach it to AgentChatHttpError.
    4.Frontend UI:
    • Catches 429 and disables the composer input textarea and submit button.
    • Updates the textarea placeholder to "Context limit reached." so the user cannot retry message turns on an exhausted session.
    • Skips the secondary non-streaming fallback request when a stream fails with 429.
  3. E2E Tests: Added a Playwright integration test verifying that hitting 429 displays the context limit message, disables the input, and changes the placeholder.

Verification

  • pytest passed: 539 passed
  • Playwright E2E passed: 13 passed
  • TypeScript check: passed

AmitAvital1 and others added 4 commits July 28, 2026 20:58
Let a user see their past conversations, start a new one, and switch
between them, backed by the agent manager as the source of truth.

Backend:
- Repository.list_sessions(user_id) + rename_session (port, memory, sql)
- GET /conversations?user_id=... returns id, title, last_message_at
  (most-recently-active first); ConversationService.list_conversations
- thread_title() derives a title from the first user message; the service
  sets it on the first turn of a conversation

Widget (stateless; BE owns the list and titles):
- anonymous per-browser user id in localStorage, sent on create/list; an
  optional <agent-chat user="..."> attribute overrides it, leaving the
  seam for real host identity later
- AgentChatClient.listConversations + useConversation listThreads/
  switchTo/startNew
- header history + new-chat buttons and a slide-over thread drawer;
  switching loads that thread's messages and usage; drawer is inert when
  closed. Degrades to empty list against a backend without the endpoint

Tests: listing scoped by user, auto-title, and a widget e2e for the
drawer (list, switch, new chat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… user

Review feedback on extra-org#59, all three points:

1. The widget remembered the active conversation under a key built from the
   endpoint alone, so a second user on the same browser (shared machine, or a
   host app signing someone else in via `<agent-chat user="...">`) resumed the
   previous user's chat. The key is now `agent-chat:<endpoint>:<userId>`.

2. `user_id` was sent when a conversation was created but not on send/stream,
   so the run executed with `RunContext.user_id = None` — exactly the field
   hooks and tools authorize on. The service now loads the session and takes
   the identity from it: a caller that omits user_id runs as the session's
   owner, and one that supplies a *different* user_id is refused with
   ConversationAccessDenied (403) instead of being silently rebound. The
   browser no longer decides who it is on a turn.

3. `GET /conversations?user_id=` stays a scoping parameter, not an
   authorization boundary — now stated in the route's docstring, with the
   place to put real auth.

`_require` returns the session it already had to fetch, so the ownership check
costs no extra query.

Tests: a turn with no client user_id runs as the owner; a mismatched user_id
raises and persists nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Show a bouncing-dots indicator while the assistant has no answer text
yet, and keep it visible when the user leaves a streaming thread and
returns.

Messages now live in a per-conversation map keyed by the id captured at
submit time, so switching threads only swaps the view — the in-flight
stream keeps writing to its own bucket. `typing` means "stream in
flight" (set at submit, cleared at completion); reduceStreamEvent is
reduced to content accumulation only, fixing dots that could otherwise
linger on an empty final answer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback on extra-org#60: stream(), send(), loadHistory() and loadUsage()
each re-read the active conversation id from localStorage when they ran.
A turn that started in thread A but finished after the user switched to B
could retry, or refresh usage, against B. Storage now only remembers which
thread was selected last; it never decides where an in-flight request goes.

- Conversation methods take the id: send(id, text), stream(id, text),
  loadHistory(id), loadUsage(id). The app already captures the id at submit
  time for its per-conversation message map, so it passes that same id.
- Usage moves into a per-conversation map alongside messages, so a late
  response cannot paint another thread's number.
- Stale-conversation recovery (404 -> create a new one) now reports the
  replacement through `onReplaced`, and the app re-keys that thread's
  messages onto the id the turn actually ran under instead of leaving them
  stranded under the dead one.

Test: a turn whose stream fails after the user switched threads retries on
its own conversation, and the usage refresh follows it — both landed on the
other thread before this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/agent_manager/api/static/widget/react/AgentChatApp.tsx Outdated
@rishu685
rishu685 force-pushed the fix/issue-50-budget-exceeded-message branch 2 times, most recently from 85d2868 to 45e5520 Compare July 29, 2026 11:46
Comment thread src/agent_manager/api/static/widget/react/AgentChatApp.tsx Outdated
Comment thread .gitignore Outdated

@AmitAvital1 AmitAvital1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please rebase your branch fix on top of this feat/widget-message-loading working branch (#60)?
And please attach screenshot that its work well now.
thanks

@rishu685
rishu685 force-pushed the fix/issue-50-budget-exceeded-message branch from 45e5520 to e1c25e9 Compare July 29, 2026 19:28
@rishu685

rishu685 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author
Screenshot 2026-07-30 at 1 05 17 AM

Screenshot showing the context-limit message and disabled composer when the token budget is exceeded.

@rishu685
rishu685 changed the base branch from main to feat/widget-message-loading July 29, 2026 19:44

@AmitAvital1 AmitAvital1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work

@AmitAvital1

Copy link
Copy Markdown
Collaborator

Do not merge until parent merge!!

@AmitAvital1
AmitAvital1 force-pushed the feat/widget-message-loading branch 3 times, most recently from a10fb7b to 3fdbe7c Compare July 31, 2026 14:19
@AmitAvital1
AmitAvital1 changed the base branch from feat/widget-message-loading to main July 31, 2026 14:58
@AmitAvital1

Copy link
Copy Markdown
Collaborator

Hi, can you please rebase again on top of main and resolve conflicts? so we can merge it. thanks

@rishu685

Copy link
Copy Markdown
Contributor Author

Done!

@rishu685
rishu685 requested a review from AmitAvital1 July 31, 2026 17:39
@AmitAvital1
AmitAvital1 merged commit e49e814 into extra-org:main Aug 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Widget shows generic "Something went wrong" when a conversation hits the token budget (429)

2 participants